package com.naiterui.ehp.bp.security.service.impl;

import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.security.exception.BadRequestException;
import com.naiterui.ehp.bp.security.jwt.TokenProvider;
import com.naiterui.ehp.bp.security.properties.LoginProperties;
import com.naiterui.ehp.bp.security.properties.SecurityProperties;
import com.naiterui.ehp.bp.security.service.IAuthorizationService;
import com.naiterui.ehp.bp.security.service.IOnlineUserService;
import com.naiterui.ehp.bp.security.service.IPermissionService;
import com.naiterui.ehp.bp.security.utils.BaseJwtUser;
import com.naiterui.ehp.bp.security.utils.RsaUtils;
import com.naiterui.ehp.bp.security.vo.AuthorizationFaceInfoVO;
import com.naiterui.ehp.bp.security.vo.AuthorizationInfoVO;
import com.naiterui.ehp.bp.security.vo.AuthorizationSmsVO;
import com.naiterui.ehp.bp.security.vo.AuthorizationUserVO;
import com.naiterui.ehp.bp.security.vo.FaceIdVO;
import com.naiterui.ehp.bp.security.vo.VerificationCodeVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@AllArgsConstructor
public class AuthorizationServiceImpl implements IAuthorizationService {

  private final TokenProvider tokenProvider;
  private final LoginProperties loginProperties;
  private final IOnlineUserService onlineUserService;
  private final SecurityProperties securityProperties;
  private final IPermissionService permissionService;
  private final AuthenticationManagerBuilder authenticationManagerBuilder;
  private final String LOGIN_ERROR_KEY = "LOGIN_ERROR_KEY_";
  private final String LOGIN_SMSCODE = "LOGIN_SMSCODE";
  private final static int PASSOWRD_MAX_ERROR_COUNT = 5;
  private final String PC_LOGIN_CACHE_KEY = "PC_LOGIN_CACHE_KEY_";

  @Override
  public AuthorizationSmsVO smsCode(AuthorizationUserVO authUser) {
    log.info("发送验证码获取到的用户信息为 userName {}, password {}", authUser.getUsername(), authUser.getPassword());
    // 密码解密
    String password = RsaUtils.decryptByPrivateKey(this.securityProperties.getPrivateKey(), authUser.getPassword());
    log.info("发送验证码获取到的用户密码解密之后的信息为 {}", password);

    //图形验证码
    if (this.loginProperties.getLoginCode().isUse()) {
      // 查询验证码
      String code = RedisUtil.valueOps().getString(authUser.getUuid());
      if (StrUtil.isBlank(code)) {
        throw new BadRequestException("验证码不存在或已过期");
      }
      if (StrUtil.isBlank(authUser.getCode()) || !this.loginProperties.verify(code, authUser.getCode())) {
        throw new BadRequestException("验证码错误");
      }
    }
    String username = authUser.getUsername();
    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
    try {
      this.authenticationManagerBuilder.getObject().authenticate(authenticationToken);
    } catch (AuthenticationException e) {
      Long incr = RedisUtil.keyOps().incr(this.LOGIN_ERROR_KEY + username);
      RedisUtil.keyOps().expire(this.LOGIN_ERROR_KEY + username, 30, TimeUnit.MINUTES);
      if (incr > PASSOWRD_MAX_ERROR_COUNT) {
          log.error("{} 账户连续{}次登录失败，账户被锁定30分钟", username, incr);
          throw new LockedException("密码连续输入错误次数过多，账户已被锁定!");
      }
      throw new BadCredentialsException(
          "您输入的用户名或者密码不正确，为保证账户安全，连续5次输入错误，系统将锁定您的账户30分钟，当前剩余:" + (PASSOWRD_MAX_ERROR_COUNT - incr) + "次", e);
    }
    // 发送短信验证码
    if(RedisUtil.keyOps().existsKey(LOGIN_SMSCODE)){
      return AuthorizationSmsVO.builder().sendResult("短信已发送，请注意查收").build();
    }
    String sendResult = permissionService.sendSmsCode(authUser.getUsername());
    return AuthorizationSmsVO.builder().sendResult(sendResult).build();
  }

  @Override
  public AuthorizationInfoVO login(AuthorizationUserVO authUser) throws AuthenticationException {
    // 密码解密
    String password = RsaUtils.decryptByPrivateKey(this.securityProperties.getPrivateKey(), authUser.getPassword());

    //图形验证码
    if (this.loginProperties.getLoginCode().isUse()) {
      // 查询验证码
      String code = RedisUtil.valueOps().getString(authUser.getUuid());
      // 清除验证码
      RedisUtil.keyOps().delete(authUser.getUuid());
      if (StrUtil.isBlank(code)) {
        throw new BadRequestException("验证码不存在或已过期");
      }
      if (StrUtil.isBlank(authUser.getCode()) || !this.loginProperties.verify(code, authUser.getCode())) {
        throw new BadRequestException("验证码错误");
      }
    }
    String username = authUser.getUsername();
    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
    Authentication authentication;
    try {
      authentication = this.authenticationManagerBuilder.getObject().authenticate(authenticationToken);
      // 发送短信验证码
      if (!RedisUtil.keyOps().existsKey(LOGIN_SMSCODE)) {
        permissionService.verifySmsCode(authUser.getUsername(), authUser.getSmsCode());
      }
    } catch (AuthenticationException | BadRequestException e) {
      Long incr = RedisUtil.keyOps().incr(this.LOGIN_ERROR_KEY + username);
      RedisUtil.keyOps().expire(this.LOGIN_ERROR_KEY + username, 30, TimeUnit.MINUTES);
      if (incr > 5) {
        log.error("{} 账户连续{}次登录失败，账户被锁定30分钟", username, incr);
        throw new LockedException("密码连续输入错误次数过多，账户已被锁定!");
      }
      throw new BadCredentialsException(
              "您输入的用户名、密码或验证码不正确，为保证账户安全，连续5次输入错误，系统将锁定您的账户30分钟，当前剩余:" + (PASSOWRD_MAX_ERROR_COUNT - incr) + "次", e);
    }

    // 处理人脸识别逻辑
    FaceIdVO faceIdVO = permissionService.verifyFace(authUser.getUsername());
    if (!faceIdVO.getCheckResult()) {
      RedisUtil.valueOps().setEx(this.PC_LOGIN_CACHE_KEY + username, authUser.getPassword(), 60 * 5);
      log.info("需要进行人脸识别认证 faceIdVO:{}", faceIdVO);
      // throw new com.naiterui.ehp.bp.security.exception.AuthenticationException("请进行人脸识别验证", -999, faceIdVO);
      return new AuthorizationInfoVO(faceIdVO);
    }

    SecurityContextHolder.getContext().setAuthentication(authentication);
    // 生成令牌
    String token = this.tokenProvider.createToken(authentication);
    final BaseJwtUser baseJwtUser = (BaseJwtUser) authentication.getPrincipal();
    // 保存在线信息
    this.onlineUserService.save(baseJwtUser, token);

    if (this.loginProperties.isSingleLogin()) {
      //踢掉之前已经登录的token
      this.onlineUserService.checkLoginOnUser(username, token);
    }
    return new AuthorizationInfoVO(this.securityProperties.getHeader(), this.securityProperties.getTokenStartWith() + token, baseJwtUser);
  }

  @Override
  public AuthorizationFaceInfoVO faceLogin(String faceId, String username) throws AuthenticationException {
    String rawPassword = RedisUtil.valueOps().getString(this.PC_LOGIN_CACHE_KEY + username);
    // PC人脸二维码超时
    if (rawPassword == null) {
      return AuthorizationFaceInfoVO.builder().expired(true).build();
    }
    String password = RsaUtils.decryptByPrivateKey(this.securityProperties.getPrivateKey(), rawPassword);

    // 获取人脸识别结果
    String faceResult = RedisUtil.valueOps().getString("naiterui-dr|detect_h5_result_" + faceId);
    if (faceResult == null || !faceResult.equals("0")) {
      return AuthorizationFaceInfoVO.builder().result(false).expired(false).build();
    }

    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
    Authentication authentication = this.authenticationManagerBuilder.getObject().authenticate(authenticationToken);
    SecurityContextHolder.getContext().setAuthentication(authentication);
    // 生成令牌
    String token = this.tokenProvider.createToken(authentication);
    final BaseJwtUser baseJwtUser = (BaseJwtUser) authentication.getPrincipal();
    // 保存在线信息
    this.onlineUserService.save(baseJwtUser, token);

    if (this.loginProperties.isSingleLogin()) {
      //踢掉之前已经登录的token
      this.onlineUserService.checkLoginOnUser(username, token);
    }
    return new AuthorizationFaceInfoVO(this.securityProperties.getHeader(), this.securityProperties.getTokenStartWith() + token, baseJwtUser, true, false);
  }

  @Override
  public VerificationCodeVO getCode() {
    // 获取运算的结果
    AbstractCaptcha captcha = this.loginProperties.getCaptcha();
    String uuid = this.securityProperties.getCodeKey() + IdUtil.simpleUUID();
    // 保存
    RedisUtil.valueOps().setString(uuid, captcha.getCode(), this.loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES);
    return new VerificationCodeVO("data:image/png;base64," + captcha.getImageBase64(), uuid);
  }
}
